package Thread;

import java.time.LocalDateTime;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * 并发之可见性
 *    下面三者的性能耗时：CPU < 内存 < I/O
 *    java内存模型中，什么叫做：可见性、有序性、原子性。
 *         原子性：原子性指的是一个操作不会被中断，操作不会受到其他线程的影响。两个线程同时对一个变量赋值，则该值要么是1要么是2，线程A和线程B互不干扰，不会被中断。
 *         可见性：可见性指的是一个线程修改共享变量的值，其他线程能够马上得到这个修改的值。比如线程A修改了共享变量的值，那么线程B读取的就是线程A改变之后的值。
 *         有序性：有序性指的是单线程中代码的执行总是按照循序执行的，多线程中可能出现乱序顺序，程序编译成字节码指令的时候可能会出现重排序现象，重排后的指令与原指令的顺序比一定一致。
 *                另一种角度:在一个线程内所有的操作都是有序的，但是在多线程下，一个线程观察另一个线程，所有的操作都是无序的，因为可能存在指令重排序和工作内存和主内存的同步延迟。编译器和处理器的重排序有编译器优化重排序，指令级并行重排序，内存系统的重排序
 *    可见性问题：既工作内存和主内存不可见概念，当线程改变共享变量时候，不是直接修改主内存的变量，而是读取一份到自己的工作内存中，修改完值再存到主内存，当线程A在自己的工作内存中
 *              修改值时候，主内存的值被线程B读去了，这样就产生了共享数据不一致问题。
 *              可见性问题都是由CPU缓存不一致为并发编程带来的，主要有三种情况：
 *               1.线程交叉执行：线程A在执行过程中切换到线程B执行完成后，再切换回线程A执行剩下的操作；此时线程B对变量的修改不能对线程A立即可见，这就导致了计算结果和理想结果不一致的情况。
 *               2.重排序结合线程交叉执行
 *               3.共享变量更新后的值没有在工作内存及主存间及时更新
 *    java 7种基本数据类型：7种数据类型中，对于32位操作系统，boolean,byte,char,short,int,float都是原子操作，long,double要进行两次读取，不是原子性，但是大部分的虚拟机
 *    现在都是64位，所以也是原子操作
 *
 *       boolean   8位    1字节
 *       byte      8位    1字节
 *       char     16位    2字节
 *       short    16位    2字节
 *       int      32位    4字节
 *       float    32位    4字节
 *       long     64位    8字节
 *       double   64位    8字节
 */
public class Example_Atomic {
    // 状态标识flag
    /**
     * 当给flage 添加volatile关键字
     * volatile 关键字是Java虚拟机提供的最轻量级的同步机制.
     * 就是用volatile修饰的变量，对这个变量的读写，不能使用 CPU 缓存，必须从内存中读取或者写入。
     */
    private static    boolean flag = true;

    private static AtomicBoolean  atomicBoolean=new AtomicBoolean(true);

    /**
     *理想状态下。主线程将flag设置成false，那么子线程应该跳出循环，但是发现程序一直没有停止，说明子线程一直在执行，既子线程的run()方法的while一直在循环，没有挑出来。
     * 原因就是共享变量flag没有被子线程及时可见，
     * 问题原因：上述线程可见性问题的第1条和第3条。
     *
     * 解决问题：
     *     方案一：给flag加上volatile 关键字，保证共享变量的可见性 ，程序可以保证完成退出。volatile仅仅保持可见性
     *     方案二：使用Atomic原子类，不仅可以保持可见性，保持原子性.
     *     方案三：Lock锁，保证可见性和原子性,使用synchronized包装i++
     *
     *   synchronized:
     *      修饰实例方法：作用于当前实例加锁，进入同步代码前要获得当前实例的锁
     *      修饰静态方法：作用于当前类对象加锁，进入同步代码前要获得当前类对象的锁
     *      修饰代码块：指定加锁对象，对给定对象加锁，进入同步代码块前要获得给定对象的锁
     */
    public static void main(String[] args) throws InterruptedException {

        System.out.println(Integer.MAX_VALUE);

        System.out.println(LocalDateTime.now() + "：主线程启动计数子线程");
        new CountThread().start();

        Thread.sleep(1000);
        // 设置flag为false，使上面启动的子线程跳出while循环，结束运行
        Example_Atomic.flag = false;
       // atomicBoolean.set(false);
        System.out.println(LocalDateTime.now() + "主线程将状态标识flag被置为false了");
    }

    static class CountThread extends Thread {
        @Override
        public void run() {
            System.out.println(LocalDateTime.now() + "计数子线程start计数");
            int i = 0;
            while (flag) {
              //  i++;
                synchronized (this){
                    i++;
                }
            }
            System.out.println(LocalDateTime.now() + "计数子线程end计数，运行结束：i的值是: " + i);
        }
    }

}

